/*
 * Copyright 2011 Michele Mancioppi [michele.mancioppi@gmail.com]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cave.nice.testMessage.resources;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import cave.nice.testMessage.TestMessageConstants;
import cave.nice.testMessage.data.CannotRetrieveEntitiesException;
import cave.nice.testMessage.data.DataManager;
import cave.nice.testMessage.data.UnknownUnverifiedAccountException;
import cave.nice.testMessage.data.UnknownVerifiedAccountException;
import cave.nice.testMessage.data.UnverifiedAccount;
import cave.nice.testMessage.data.VerifiedAccount;
import cave.nice.testMessage.templates.TemplateException;
import cave.nice.testMessage.templates.TemplateManager;
import cave.nice.testMessage.templates.TemplateManager.TemplateType;

import com.google.common.collect.Maps;

@Path("/unverifiedAccounts")
public class UnverifiedAccountResource extends Resource {

	@Context
	private UriInfo uriInfo;

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response getUnverifiedAccounts() {
		DataManager dataManager = getDataManager();
		try {
			return Response.ok(dataManager.getUnverifiedAccounts()).build();
		} catch (CannotRetrieveEntitiesException e) {
			// TODO Log error, not return it
			throw new WebApplicationException(Response.serverError().entity(e)
					.build());
		} finally {
			dataManager.close();
		}
	}

	@POST
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	public Response addUnverifiedAccount(@Context ServletContext context,
			@FormParam("emailAddress") InternetAddress emailAddress)
			throws WebApplicationException {
		if(emailAddress == null) {
			throw new MissingRequiredParameterException("emailAddress");
		}

		DataManager dataManager = getDataManager();
		UnverifiedAccount newAccount = null;
		try {
			UnverifiedAccount unverifiedAccount = dataManager
					.getUnverifiedAccount(emailAddress);
			throw new WebApplicationException(
					Response.status(Status.BAD_REQUEST)
							.entity("The submitted email address '"
									+ emailAddress
									+ "' results already registered (but not yet verified!) since "
									+ unverifiedAccount.getRegistrationDate())
							.build());
		} catch (UnknownUnverifiedAccountException e1) {
			try {
				VerifiedAccount verifiedAccount = dataManager
						.getVerifiedAccount(emailAddress);
				throw new WebApplicationException(Response
						.status(Status.BAD_REQUEST)
						.entity("The submitted email address '" + emailAddress
								+ "' results already registered since "
								+ verifiedAccount.getRegistrationDate())
						.build());
			} catch (UnknownVerifiedAccountException e) {
				/*
				 * All right, that is how we like it ;-)
				 */
				try {
					newAccount = dataManager
							.createUnverifiedAccount(emailAddress);
					performSendConfirmation(context, newAccount);

					URI accountURI = uriInfo.getBaseUri().resolve(
							"/resources/unverifiedAccounts/"
									+ newAccount.getEmailAddress());

					return Response.created(accountURI).build();
				} catch (WebApplicationException e2) {
					dataManager.removeAccount(newAccount);

					throw e2;
				}
			}
		} finally {
			dataManager.close();
		}
	}

	@POST
	@Path("/withMethodOverride")
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	public Response processMethodOvveride(@Context ServletContext context,
			@FormParam("_method") String method,
			@FormParam("emailAddress") InternetAddress emailAddress,
			@FormParam("challenge") UUID challenge)
			throws WebApplicationException {
		if(method == null) {
			throw new MissingRequiredParameterException("_method");
		}

		if(emailAddress == null) {
			throw new MissingRequiredParameterException("emailAddress");
		}

		String methodLC = method.toLowerCase();
		if (methodLC.equals("put")) {
			return verifyAccount(emailAddress, challenge);
		} else if (methodLC.equals("delete")) {
			return deleteUnverifiedAccount(emailAddress);
		}

		throw new WebApplicationException(Response
				.status(Status.BAD_REQUEST)
				.entity("Unrecognize value for the parameter '_method': "
						+ method).build());
	}

	@PUT
	@Path("/{emailAddress}/confirmation")
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	public Response verifyAccount(
			@PathParam("emailAddress") InternetAddress emailAddress,
			@FormParam("challenge") UUID challenge)
			throws WebApplicationException {
		if (challenge == null) {
			throw new WebApplicationException(Response
					.status(Status.BAD_REQUEST)
					.entity("Mandatory parameter 'challenge' not set").build());
		}

		DataManager dataManager = getDataManager();
		try {
			dataManager.verifyAccount(emailAddress, challenge);
			return Response.ok().build();
		} catch (Exception e) {
			throw new WebApplicationException(
					Response.status(Status.BAD_REQUEST)
							.entity("Could not verify account with challenge '"
									+ challenge
									+ "'. Either the challenge is wrong, or the email address specified is not pending verification (it may altogether not existing in the system).")
							.build());
		} finally {
			dataManager.close();
		}
	}

	@POST
	@Path("/{emailAddress}/verificationMessageRequest")
	public void sendConfirmation(@Context ServletContext context,
			@PathParam("emailAddress") InternetAddress emailAddress)
			throws WebApplicationException {
		DataManager dataManager = getDataManager();
		try {
			performSendConfirmation(context,
					dataManager.getUnverifiedAccount(emailAddress));
		} catch (UnknownUnverifiedAccountException e) {
			throw new WebApplicationException(Status.NOT_FOUND);
		} finally {
			dataManager.close();
		}
	}

	@DELETE
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	public Response deleteUnverifiedAccount(
			@FormParam("emailAddress") InternetAddress emailAddress)
			throws WebApplicationException {
		try {
			// TODO Fix validation
			emailAddress.validate();
		} catch (AddressException e1) {
			throw new InvalidEmailAddressException(emailAddress);
		}

		DataManager dataManager = getDataManager();
		try {
			UnverifiedAccount account = dataManager
					.getUnverifiedAccount(emailAddress);
			dataManager.removeAccount(account);
			/*
			 * We return a 202 "Accepted" instead of a 204 "No content" because
			 * the modification could date some time to be propagated
			 */
			return Response.status(Status.ACCEPTED).build();
		} catch (UnknownUnverifiedAccountException e) {
			throw new WebApplicationException(e);
		} finally {
			dataManager.close();
		}
	}

	public void performSendConfirmation(ServletContext context,
			UnverifiedAccount unverifiedAccount) throws WebApplicationException {
		InternetAddress appInternetAddress = null;
		try {
			appInternetAddress = new InternetAddress(
					TestMessageConstants.NOTIFICATIONS_EMAIL_ADDRESS,
					TestMessageConstants.TEST_MESSAGE_SENDER_NAME);
		} catch (UnsupportedEncodingException e) {
			throw new WebApplicationException(
					Response.serverError()
							.entity("Error while creating the app internet address using the email '"
									+ TestMessageConstants.NOTIFICATIONS_EMAIL_ADDRESS
									+ "' and the personal name '"
									+ TestMessageConstants.TEST_MESSAGE_SENDER_NAME
									+ "'").entity(e).build());
		}

		DataManager dataManager = getDataManager();
		try {
			Session session = Session
					.getDefaultInstance(new Properties(), null);

			String notificationMailAddress = unverifiedAccount
					.getEmailAddress();
			Message msg = null;
			try {
				msg = new MimeMessage(session);
				msg.setFrom(appInternetAddress);
				msg.addRecipient(Message.RecipientType.TO, new InternetAddress(
						notificationMailAddress));
				msg.setReplyTo(new InternetAddress[] { appInternetAddress });
				msg.setSubject("Confirm the registration of your account '"
						+ unverifiedAccount.getEmailAddress()
						+ "' to Test Message");
				msg.setContent(
						getConfirmationEmail(context, unverifiedAccount),
						"text/html");

				Transport.send(msg);
			} catch (Exception e) {
				throw new WebApplicationException(Response
						.status(Status.INTERNAL_SERVER_ERROR)
						.entity("Error while sending the report to '"
								+ notificationMailAddress + "': " + msg)
						.entity(e).build());
			}
		} finally {
			dataManager.close();
		}
	}

	private String getConfirmationEmail(ServletContext context,
			UnverifiedAccount account) throws TemplateException {
		Map<String, Object> params = Maps.newHashMap();
		params.put("username", getCurrentUser().getNickname());
		params.put("emailAddress", account.getEmailAddress());
		URI confirmationURI = uriInfo.getBaseUri().resolve(
				"/confirmEmailAccount.jsp?emailAddress=" + account.getEmailAddress()
						+ "&challenge=" + account.getChallenge());

		params.put("confirmationUrl", confirmationURI.toString());
		URI setupURI = uriInfo.getBaseUri().resolve("/instructions.jsp");
		params.put("setupUrl", setupURI.toString());

		return TemplateManager.getInstance(context).applyTemplate(
				TemplateType.CONFIRMATION_REGISTRATION, params);
	}

}